Skip to content

fix(rsc): pre-include React peers in client env optimizeDeps (fix #1213)#1214

Closed
jgeurts wants to merge 2 commits into
vitejs:mainfrom
jgeurts:fix/plugin-rsc-client-env-react-deps
Closed

fix(rsc): pre-include React peers in client env optimizeDeps (fix #1213)#1214
jgeurts wants to merge 2 commits into
vitejs:mainfrom
jgeurts:fix/plugin-rsc-client-env-react-deps

Conversation

@jgeurts
Copy link
Copy Markdown

@jgeurts jgeurts commented May 7, 2026

fix #1213

Summary

The client environment's optimizeDeps.include only listed
react-dom/client and one React Server DOM entry — not react,
react/jsx-runtime, or any of the React-peer packages crawled by
crawlFrameworkPkgs. Those got discovered lazily as the browser
requested modules, the optimizer re-ran mid-load, and the ?v= hash
changed on chunks that had already been served. The browser ended up
with two distinct React module records, and 'use client' libraries
that synchronously call hooks in providers hit the wrong dispatcher
(React.H is null), throwing Invalid hook call.

The ssr and rsc envs don't hit this because their
optimizeDeps.include already lists react, react-dom,
react/jsx-runtime, and react/jsx-dev-runtime.

Change

Mirror the SSR/RSC env behaviour for the client env: pre-include
those four React subpaths plus the React-peer packages that
crawlFrameworkPkgs already discovers for noExternal. Filter that
spread to packages whose bare specifier resolves from the user's
project root — server-only React peers (e.g. the
@vitejs/test-dep-client-in-server test fixture, which only exports
./server) would otherwise break dep optimization.

Reproduction

Repro repo: https://github.com/jgeurts/vite-rsc-cjs-subpath-named-exports

git clone https://github.com/jgeurts/vite-rsc-cjs-subpath-named-exports
cd vite-rsc-cjs-subpath-named-exports
npm install
rm -rf node_modules/.vite
npm run dev
# open http://localhost:5173/

The page mounts, then the browser console shows two Invalid hook call
errors and a TypeError: Cannot read properties of null (reading 'useMemo')
where the React, @liveblocks_react, and react-dom_client chunks each
carry a different ?v= hash. With this PR applied (drop the patched
@vitejs/plugin-rsc into the repro's node_modules), the page renders
cleanly.

Verification

  • All 424 plugin-rsc unit tests pass (pnpm test).
  • examples/basic runs cleanly in pnpm dev and pnpm build after
    the change. The fixture exercises a wide range of 'use client' /
    'use server' paths, including the @vitejs/test-dep-client-in-server
    fixture that lacks a . export — confirms the resolution-based filter
    works.
  • New regression test e2e/use-client-hook-in-provider.test.ts stands
    up an isolated dev fixture with a 'use client' package that calls
    useMemo synchronously in a Provider. With the fix it passes; with
    the fix reverted it fails the dev case (the build case still passes —
    this bug is dev-only).
  • Repro repo (linked above) loads cleanly with the patched plugin.

@jgeurts jgeurts force-pushed the fix/plugin-rsc-client-env-react-deps branch from 7977aeb to 91e8832 Compare May 7, 2026 04:08
The client environment only had `react-dom/client` and the React
Server DOM browser entry in `optimizeDeps.include`. React itself
and crawled React-peer packages were discovered lazily as the
browser requested modules, which re-runs the optimizer mid-load
and changes the `?v=` hash on already-served chunks. The browser
ends up with two distinct React module records, and `'use client'`
libraries that synchronously call hooks in providers hit the wrong
dispatcher (`React.H` is null), throwing `Invalid hook call`.

Mirror the SSR/RSC env behaviour by including `react`,
`react-dom`, `react/jsx-runtime`, `react/jsx-dev-runtime`, plus
the React-peer packages from `crawlFrameworkPkgs`. Filter that
list to packages whose bare specifier resolves from the user's
project root, so server-only React peers (e.g. the
`@vitejs/test-dep-client-in-server` fixture, which only exports
`./server`) don't break dep optimization.
@jgeurts jgeurts force-pushed the fix/plugin-rsc-client-env-react-deps branch from 91e8832 to 2eeb6ac Compare May 7, 2026 04:09
Stands up an isolated dev fixture with a `'use client'` third-party
package that calls `useMemo` synchronously in a Provider. Without
the client env `optimizeDeps.include` fix, lazy discovery on first
request changes the React `?v=` hash mid-load, the browser ends up
with two React module records, and `Invalid hook call` fires.

Watches both `pageerror` and `console` events so React errors that
the dev error boundary recovers from still fail the assertion.
@hi-ogawa
Copy link
Copy Markdown
Contributor

hi-ogawa commented May 7, 2026

Thanks for the PR. Closing for now. See the rational in #1213 (comment)

@hi-ogawa hi-ogawa closed this May 7, 2026
@jgeurts
Copy link
Copy Markdown
Author

jgeurts commented May 7, 2026

Thanks for the quick review and the optimizeDeps.entries pointer — closing this PR. Setting environments.client.optimizeDeps.entries: ['src/**/*.{ts,tsx}'] in our own framework config (Vinext-based downstream) eagerly discovers the 'use client' React-peer packages and resolves the cold-start drift cleanly. Verified end-to-end on the minimal repro and on our production app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-rsc: cold-start client optimizer drift causes Invalid hook call in 'use client' libs

2 participants